using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.ExceptionServices;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;

namespace Extensions.Configuration.Database
{
    /// <summary>
    /// An database based <see cref="ConfigurationProvider"/>.
    /// (数据库配置供应者实现)
    /// </summary>
    public class DatabaseConfigurationProvider : ConfigurationProvider, IDisposable
    {
        public DbContextOptions<ConfigurationDatabaseContext> DbContextOptions { get; }

        private readonly IDisposable _changeTokenRegistration;
        private DatabaseConfigurationSource Source { get; }

        public DatabaseConfigurationProvider(DatabaseConfigurationSource source)
        {
            Source = source ?? throw new ArgumentNullException(nameof(source));

            var builder = new DbContextOptionsBuilder<ConfigurationDatabaseContext>();
            Source.DatabaseOptions?.Invoke(builder);
            DbContextOptions = builder.Options ?? throw new ArgumentNullException(nameof(DbContextOptions));

            if (Source.ReloadOnChange && Source.DatabaseProvider != default)
            {
                // ChangeToken.OnChange 用法参考：https://www.cnblogs.com/jionsoft/p/12249326.html
                _changeTokenRegistration = ChangeToken.OnChange(
                    () => Source.DatabaseProvider.Watch(DbContextOptions),
                    Load);
            }
        }

        private void Load(bool reload)
        {
            try
            {
                foreach (var item in this.Source.DatabaseProvider.Load(this.DbContextOptions))
                {
                    if (this.Data.ContainsKey(item.Key))
                    {
                        // 如果有重复的，那么移除之前的，添加最新的
                        this.Data.Remove(item.Key);
                    }

                    this.Data.TryAdd(item.Key, item.Value);
                }
            }
            catch (Exception ex)
            {
                if (reload)
                {
                    base.Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                }

                var exception = new InvalidDataException("加载数据异常", ex);
                HandleException(ExceptionDispatchInfo.Capture(exception));
            }

            base.OnReload();
        }

        private void HandleException(ExceptionDispatchInfo info)
        {
            var ignoreException = false;
            if (this.Source.OnLoadException != default)
            {
                var exceptionContext = new DatabaseLoadExceptionContext
                {
                    Provider = this,
                    Exception = info.SourceException
                };
                this.Source.OnLoadException.Invoke(exceptionContext);
                ignoreException = exceptionContext.Ignore;
            }

            if (!ignoreException)
            {
                info.Throw();
            }
        }

        /// <summary>
        /// 重写父类加载方法
        /// </summary>
        public override void Load()
        {
            this.Load(reload: false);
        }

        /// <inheritdoc />
        public void Dispose() => Dispose(true);

        /// <summary>
        /// Dispose the provider.
        /// </summary>
        /// <param name="disposing"><c>true</c> if invoked from <see cref="IDisposable.Dispose"/>.</param>
        protected virtual void Dispose(bool disposing)
        {
            _changeTokenRegistration?.Dispose();
        }
    }
}